#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import re
import sys
import json
import collections.abc


TEMPLATE_FULLFN = "/usr/share/rcagent.conf.in"

TARGET_FULLFN = "/etc/rcagent.conf"


class JsonTemplate:

    # exmple template (tpl.json):
    # {
    #    "key": "hard coded value",
    #    "key2": 1,
    #    "key3": "{{ variable }}",
    #    "key4": "{% array %}"
    # }
    #
    # example code:
    #     obj = JsonTemplates(json_fn="tpl.json")
    #     data = obj.generate({"variable": "abc", "array": [1, 2, 3]})
    #     pathlib.Path("output.json").write_text(data)
    #
    # example result (output.json):
    # {
    #     "key": "hard coded value",
    #     "key2": 1,
    #     "key3": "abc",
    #     "key4": [1, 2, 3]
    # }
    #

    def __init__(self, json_obj=None, json_str=None, json_fn=None):
        if json_obj is not None:
            assert json_str is None and json_fn is None
            self.__template = json_obj
        elif json_str is not None:
            assert json_obj is None and json_fn is None
            self.__template = json.loads(json_str)
        elif json_fn is not None:
            assert json_obj is None and json_str is None
            with open(json_fn) as f:
                self.__template = json.load(f)
        else:
            raise Exception("No json data provided.")

        self.__var_regex = re.compile(r"\{\{ [a-zA-Z0-9_]+ \}\}")
        self.__arr_regex = re.compile(r"\{\% [a-zA-Z0-9_]+ \%\}")
        self.__cln_regex = re.compile(r"[a-zA-Z0-9_]+")

    def generate(self, replace_dict):
        return self.__scan_nodes(self.__template, replace_dict)

    def __scan_nodes(self, nodes, replace_dict):
        for k, v in nodes.items():
            if isinstance(v, collections.abc.Mapping):
                nodes[k] = self.__scan_nodes(v, replace_dict)
            else:
                bSuccess, data = self.__evalulate_value(v, replace_dict)
                if bSuccess:
                    # data contains replaced value
                    nodes[k] = data
                else:
                    # data contains error message
                    raise Exception(f'Unable to find a replacement value for "{k}", {data}')
        return nodes

    def __evalulate_value(self, value, replace_dict):
        if self.__var_regex.fullmatch(str(value)):
            replace_key = self.__clean_key(value)
            if not replace_key[0]:
                return (False, replace_key[1])
            replace_key = replace_key[1]
            if replace_key in replace_dict:
                return (True, replace_dict[replace_key])
            else:
                return (False, f'"{replace_key}" not found in replacement dictionary.')
        elif self.__arr_regex.fullmatch(str(value)):
            replace_key = self.__clean_key(value)
            if not replace_key[0]:
                return (False, replace_key[1])
            replace_key = replace_key[1]
            if replace_key in replace_dict:
                if self.__is_iterable(replace_dict[replace_key]):
                    return (True, replace_dict[replace_key])
                else:
                    return (False, f"{replace_key} value must be iterable.")
            else:
                return (False, f'"{replace_key}" not found in replacement dictionary.')
        else:
            return (True, value)

    def __is_iterable(self, obj):
        try:
            iter(obj)
            return True
        except BaseException:
            return False

    def __clean_key(self, key):
        mtch = self.__cln_regex.search(key)
        if mtch is None:
            return (False, f'"{key}" is not valid!')
        else:
            return (True, mtch.group(0))


class Util:

    @staticmethod
    def ensureAncesterDir(filename):
        assert os.path.isabs(filename)
        assert os.path.normpath(filename) == filename
        os.makedirs(os.path.dirname(filename), exist_ok=True)


if __name__ == "__main__":
    data = json.loads(sys.argv[1])
    ret = JsonTemplate(json_fn=TEMPLATE_FULLFN).generate(data)
    Util.ensureAncesterDir(TARGET_FULLFN)
    with open(TARGET_FULLFN, "w") as f:
        json.dump(ret, f, indent=4)
